package think.in.spring.web.processor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import think.in.spring.web.annotation.ExecutionTime;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class ExecutionTimeBeanPostProcessor implements BeanPostProcessor {
    // 缓存带有@ExecutionTime注解的方法
    private final Map<String, Map<Method, ExecutionTime>> annotatedMethodsCache = new HashMap<>();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 扫描bean类中带有@ExecutionTime注解的方法
        Class<?> beanClass = bean.getClass();
        Map<Method, ExecutionTime> annotatedMethods = new HashMap<>();

        ReflectionUtils.doWithMethods(beanClass, method -> {
            ExecutionTime annotation = AnnotationUtils.findAnnotation(method, ExecutionTime.class);
            if (annotation != null) {
                annotatedMethods.put(method, annotation);
            }
        });

        if (!annotatedMethods.isEmpty()) {
            annotatedMethodsCache.put(beanName, annotatedMethods);
        }

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 如果bean有需要监控的方法，则创建代理
        if (annotatedMethodsCache.containsKey(beanName)) {
            Map<Method, ExecutionTime> methodExecutionTimeMap = annotatedMethodsCache.get(beanName);
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            proxyFactory.addAdvice(new AdviceTest(methodExecutionTimeMap));
            return proxyFactory.getProxy();
        }
        return bean;
    }

    public static class AdviceTest implements MethodInterceptor{
        private final Map<Method, ExecutionTime> methodExecutionTimeMap;
        public AdviceTest(Map<Method, ExecutionTime> methodExecutionTimeMap){
            this.methodExecutionTimeMap = methodExecutionTimeMap;
        }
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Method invokedMethod = invocation.getMethod();
            ExecutionTime annotation = methodExecutionTimeMap.get(invokedMethod);
            if (annotation != null) {
                long start = System.nanoTime();
                try {
                    return invocation.proceed();
                } finally {
                    long duration = System.nanoTime() - start;
                    ;
                    System.out.println(invokedMethod.getName() + "调用耗时:" +
                            convertDuration(duration,annotation.unit()));
                }
            }
            return invocation.proceed();
        }
        private long convertDuration(long nanos, TimeUnit unit) {
            switch (unit) {
                case NANOSECONDS: return nanos;
                case MICROSECONDS: return nanos / 1_000;
                case MILLISECONDS: return nanos / 1_000_000;
                case SECONDS: return nanos / 1_000_000_000;
                default: return nanos;
            }
        }
    }

}
